package com.sjgs.gis.utils;

import com.sjgs.gis.errors.LocalFileIOException;

import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

public class QETag {

    private static final int CHUNK_SIZE = 1 << 22;

    public static Boolean valid(String fileName, long size) throws IOException {
        Path path = Paths.get(fileName);
        if (Files.notExists(path) || !Files.isReadable(path) || Files.isDirectory(path)) {
            System.err.println("Error: File not found or not readable");
            return false;
        }
        return Files.size(path) == size;
    }

    public static String calcETag(byte[] bytes) {
        String etag = "";
        try {
            byte[] sha1Data = sha1(bytes);
            int sha1DataLen = sha1Data.length;
            byte[] hashData = new byte[sha1DataLen + 1];
            System.arraycopy(sha1Data, 0, hashData, 1, sha1DataLen);
            hashData[0] = 0x16;
            etag = urlSafeBase64Encode(hashData);
        } catch (NoSuchAlgorithmException e) {
            ;
        } finally {

        }
        return etag;
    }

    public static String calcETag(String fileName) throws IOException {
        String etag = "";
        Path path = Paths.get(fileName);
        if (Files.notExists(path) || !Files.isReadable(path) || Files.isDirectory(path)) {
            System.err.println("Error: File not found or not readable");
            return etag;
        }
        FileInputStream inputStream = null;
        try {
            long fileLength = Files.size(path);
            inputStream = new FileInputStream(fileName);
            if (fileLength <= CHUNK_SIZE) {
                byte[] fileData = new byte[(int) fileLength];
                inputStream.read(fileData, 0, (int) fileLength);
                byte[] sha1Data = sha1(fileData);
                int sha1DataLen = sha1Data.length;
                byte[] hashData = new byte[sha1DataLen + 1];
                System.arraycopy(sha1Data, 0, hashData, 1, sha1DataLen);
                hashData[0] = 0x16;
                etag = urlSafeBase64Encode(hashData);
            } else {
                int chunkCount = (int) (fileLength / CHUNK_SIZE);
                if (fileLength % CHUNK_SIZE != 0) {
                    chunkCount += 1;
                }
                byte[] allSha1Data = new byte[0];
                //改进取样以提高大文件计算速率
                int step = chunkCount < 21 ? 1 : chunkCount > 100 ? chunkCount > 500 ? chunkCount > 1000 ? 23 : 11 : 7 : 3;
                List<Integer> keys = new ArrayList<>();
                for (int i = 0; i < chunkCount - 1; i += step) {
                    keys.add(i);
                }
                //增加尾部
                keys.add(chunkCount - 1);
                for (int i = 0; i < chunkCount; i++) {
                    byte[] chunkData = new byte[CHUNK_SIZE];
                    int bytesReadLen = inputStream.read(chunkData, 0, CHUNK_SIZE);
                    //仅取少量样本进行计算
                    if (keys.contains(i)) {
                        byte[] bytesRead = new byte[bytesReadLen];
                        System.arraycopy(chunkData, 0, bytesRead, 0, bytesReadLen);
                        byte[] chunkDataSha1 = sha1(bytesRead);
                        byte[] newAllSha1Data = new byte[chunkDataSha1.length
                                + allSha1Data.length];
                        System.arraycopy(allSha1Data, 0, newAllSha1Data, 0,
                                allSha1Data.length);
                        System.arraycopy(chunkDataSha1, 0, newAllSha1Data,
                                allSha1Data.length, chunkDataSha1.length);
                        allSha1Data = newAllSha1Data;
                    }
                }
                byte[] allSha1DataSha1 = sha1(allSha1Data);
                byte[] hashData = new byte[allSha1DataSha1.length + 1];
                System.arraycopy(allSha1DataSha1, 0, hashData, 1,
                        allSha1DataSha1.length);
                hashData[0] = (byte) 0x96;
                etag = urlSafeBase64Encode(hashData);
            }
        } catch (NoSuchAlgorithmException e) {
            throw new LocalFileIOException("calcETag exception:" + fileName);
        } finally {
            if (inputStream != null)
                inputStream.close();
        }
        return etag;
    }

    private static byte[] sha1(byte[] data) throws NoSuchAlgorithmException {
        MessageDigest mDigest = MessageDigest.getInstance("sha1");
        return mDigest.digest(data);
    }

    private static String urlSafeBase64Encode(byte[] data) {
        String encodedString = DatatypeConverter.printBase64Binary(data);
        encodedString = encodedString.replace('+', '-').replace('/', '_');
        return encodedString;
    }

    public static void main(String[] args) {
        int chunk_size = 1024 * 1024 * 2;
        String path = "E:/data/xj.osm.pbf";
        File file = new File(path);
        FileInputStream fin = null;
        try {
            fin = new FileInputStream(file);
            FileChannel inChannel = fin.getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(chunk_size);
            Long index = 0L;
            Long offset = 0L;
            long count = file.length() % chunk_size == 0 ? Math.floorDiv(file.length(), chunk_size) : Math.floorDiv(file.length(), chunk_size) + 1;
            int eof = 0;
            while (true) {
                byteBuffer.rewind();
                if ((eof = inChannel.read(byteBuffer)) != -1) {
                    System.out.println(eof);
                    byteBuffer.flip();
                    offset += eof;
                    byte[] b=new byte[eof];
                    byteBuffer.get(b);
                    byteBuffer.clear();
                    String hash = QETag.calcETag(b);
                    System.out.println(index+" : "+hash+" : "+b.length);
                    index++;
                } else {
                    break;
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
